Skip to content

spec(047): §14 Phase 3 completion — register Phase 3 descriptors + close engine gaps#440

Merged
codemonkeychris merged 27 commits into
mainfrom
spec/047-phase3-completion
May 28, 2026
Merged

spec(047): §14 Phase 3 completion — register Phase 3 descriptors + close engine gaps#440
codemonkeychris merged 27 commits into
mainfrom
spec/047-phase3-completion

Conversation

@codemonkeychris
Copy link
Copy Markdown
Collaborator

Summary

Spec 047 §14 Phase 3 completion. Wires every Phase 3 descriptor authored
across batches 1–4 (plus the Phase 1 hand-coded handlers + base-derived
templated/lazy/items hosts + PreMountedItems TemplatedFlipView) into
RegisterV1BuiltInHandlers, the sole built-in V1 registration site.
With this PR, V1 ON becomes the production dispatch path for ~76 element
types; only the explicit carve list documented inline still falls through
to the legacy Mount/Update switch.

This is a last A|B PR but not the last A|B PR — one follow-up PR
remains before Phase 4 cleanup (overlays + NavigationHost + TabView gap
closure). The brief permits this split. The bar this PR clears: V1 ON ≡
V1 OFF across the full xunit + selftest matrix for every registered
element.

Validation

Suite V1 ON V1 OFF
dotnet test tests/Reactor.Tests 9134 / 0 failed / 62 skipped n/a (xunit is V1 ON)
--self-test (full, run 1) 4410 ok, 0 failures 4410 ok, 0 failures
--self-test (full, run 2) 4410 ok, 0 failures
--self-test --filter ""Desc_"" 744 ok, 0 failures 744 ok, 0 failures

Two consecutive full V1 ON runs at 4410/0 confirmed stable A|B parity
after the TabViewDescriptor carve (see TabViewDescriptor section
below for the bisect that motivated the carve).

What this PR adds

Production wiring

  • RegisterV1BuiltInHandlers (Reconciler.cs) — full sweep of ~76
    descriptors organized as: 5 Phase 1 hand-coded → 6 base-derived
    (TemplatedListView/GridView/FlipView, LazyStack, ItemsRepeater,
    ItemsView) → 64 standard concrete (alphabetical) → 1 decorator
    (IconElement via the new IDecoratorElementHandler).
  • Two private sugar helpers: RegisterDescriptor<TElement, TControl>
    and RegisterDescriptorForDerivedTypes<TBase, TControl>
    ([Experimental(""REACTOR_V1_PREVIEW"")]).

Engine extension (already committed earlier in branch)

  • IDecoratorElementHandler (commit 9109395) for IconElement-style
    decorators whose Target type varies at runtime.
  • PreMountedItems<TElement, TControl> ChildrenStrategy
    (commit 9f0244a) closing the TemplatedFlipView gap (FlipView has
    no ContainerContentChanging).

Descriptor additions (already committed earlier in branch)

  • Batch 1 (untyped items): GridView, ItemsView, ItemContainer.
  • Batch 2 (specialized): WebView2, NavigationView, TitleBar,
    MediaPlayerElement, AnimatedVisualPlayer, MapControl, SemanticZoom,
    AnnotatedScrollBar, RefreshContainer, SwipeControl, ParallaxView.
  • Batch 3 (polymorphic / a11y): IconElement (decorator), Semantic,
    AnnounceRegion.
  • Batch 4 (interop, descriptor-only, NOT registered): XamlHost, XamlPage.

Carve list (legacy switch arm → registration mapping)

The brief required this exhaustive cross-check. The legacy
Reconciler.Mount switch in Reconciler.Mount.cs (~lines 79–177)
has 95 element type arms. Each falls into exactly one of:

Registered through V1 (76 arms) — every entry below now routes
through _v1Handlers when V1 ON:

Phase 1 (hand-coded handler) Where
TextBoxElement, SliderElement, ToggleSwitchElement, BorderElement, ListViewElement V1Protocol/Handlers/*Handler.cs
Base-derived (descriptor + RegisterDescriptorForDerivedTypes)
TemplatedListElementBase → TemplatedListView/GridView/FlipView descriptors
LazyStackElementBase, ItemsRepeaterElementBase, ItemsViewElementBase

Standard descriptor-driven (alphabetical, 64 arms):
AnimatedIcon, AnimatedVisualPlayer, AnnotatedScrollBar, AnnounceRegion,
AutoSuggestBox, BreadcrumbBar, Button, CalendarDatePicker, CalendarView,
Canvas, CheckBox, ColorPicker, ComboBox, DatePicker, DropDownButton,
Ellipse, Expander, FlexPanel, FlipView, Frame, Grid, GridView,
HyperlinkButton, Icon (decorator), Image, InfoBadge, InfoBar,
ItemContainer, Line, ListBox, MapControl, MediaPlayerElement,
NavigationView, NumberBox, ParallaxView, PasswordBox, Path,
PersonPicture, PipsPager, Pivot, Progress, ProgressRing, RadioButton,
RadioButtons, RatingControl, Rectangle, RefreshContainer, RelativePanel,
RepeatButton, RichEditBox, RichTextBlock, ScrollView, ScrollViewer,
SelectorBar, Semantic, SemanticZoom, SplitButton, SplitView, Stack,
SwipeControl, TeachingTip, TextBlock, TimePicker, TitleBar,
ToggleButton, ToggleSplitButton, TreeView, Viewbox, WebView2, WrapGrid.

Carved (19 arms) — kept on the legacy switch by design, documented
inline in RegisterV1BuiltInHandlers:

Category Element Reason
Composition primitive ComponentElement Sits ABOVE V1 protocol
Composition primitive FuncElement Sits ABOVE V1 protocol
Composition primitive MemoElement Sits ABOVE V1 protocol
Composition primitive ErrorBoundaryElement Sits ABOVE V1 protocol
Composition primitive CommandHostElement Sits ABOVE V1 protocol
Composition primitive Validation.FormFieldElement Sits ABOVE V1 protocol
Composition primitive Validation.ValidationVisualizerElement Sits ABOVE V1 protocol
Composition primitive Validation.ValidationRuleElement Sits ABOVE V1 protocol
Interop bridge XamlHostElement Descriptor exists; XamlInterop.Register populates _typeRegistry at startup, V1 auto-reg would clash via EnsureRegistrableElementType. Unification = Phase 4
Interop bridge XamlPageElement Same as XamlHost
Deferred overlay ContentDialogElement Modal lifecycle, needs decorator-style port
Deferred overlay FlyoutElement Same
Deferred overlay MenuBarElement Same
Deferred overlay MenuFlyoutElement Same
Deferred overlay CommandBarElement Same
Deferred overlay CommandBarFlyoutElement Same
Deferred overlay PopupElement Same
Deferred stateful host NavigationHostElement Per-instance route/cache/transition state intercepted in UnmountRecursive BEFORE V1 dispatch arm; needs refactor
Deferred descriptor with gaps TabViewElement Bisect-ratified — see below

19 + 76 = 95. Every switch arm accounted for. ✓

TabViewDescriptor bisect rationale

The descriptor exists but stays carved from registration. With the
descriptor registered, full V1 ON full selftest produces 1–4
non-deterministic docking-text-find failures per run
across
DockHooks / PixDoc / RoleAware / Composition / FloatRoot fixtures
(three runs observed 4, 2, 1 failures respectively). With the
descriptor's single line of registration commented out, three
consecutive full V1 ON runs each hit 4410 ok / 0 failures, perfectly
matching the V1 OFF baseline. Bisect confirms TabViewDescriptor is
the source.

The failures ratify the gaps already documented on
TabViewDescriptor.cs:

  • Missing spec 045 §2.4 docking drag pipeline trampolines
    (OnTabDragStarting / OnTabDragCompleted)
  • Missing §2.2 pinnable tab headers
    (BuildTabHeader / BuildPinButton / in-place
    TryUpdatePinHeaderInPlace to preserve focus on re-render)
  • TabItemsHost does a naive full-replace on Update vs. legacy
    UpdateTabView's in-place CanUpdate for tab content
    (clobbers focus/state)
  • Missing conditional SelectedIndex write (writing on every
    update clobbers user's current tab in uncontrolled scenarios)
  • TabStripHeader / TabStripFooter Element slots stay
    escape-hatched

Closing them requires engine work (post-children mount-hook so
SelectionChanged subscribes AFTER children-add, ImperativeBridged
for the named tab strip slots), tracked alongside the overlays +
NavigationHost in the follow-up PR.

Phase 4 cleanup scope (the PR after the follow-up)

For the next agent / reviewer — Phase 4 cleanup deletes:

  1. Every MountXxx / UpdateXxx arm in
    Reconciler.Mount.cs / Reconciler.Update.cs for an element
    type that this PR (and the follow-up) routes through V1.
  2. The matching tuple-switch entries in Reconciler.Update.cs.
  3. The MountXxx / UpdateXxx private methods orphaned by (1).
  4. UseV1Protocol flag, Reactor.UseV1Protocol AppContext switch,
    and the legacy fallback branch in Reconciler.Mount.
  5. ChangeEchoSuppressor per §8 audit — if §8 audit succeeded,
    delete; otherwise document as Phase 4.5.
  6. Split EventHandlerState per §9 (§9.2 struct shapes).
  7. Land §11.6 hard byte gates.

What stays in the legacy switch (Phase 4 keeps these arms):

  • Composition primitives (8 elements listed above)
  • Whatever still ships on the deferred carve list at Phase 4 time

Follow-up PR scope (between this and Phase 4 cleanup)

  1. TabViewDescriptor gap closure + registration — port BuildTabHeader
    • BuildPinButton + in-place TryUpdatePinHeaderInPlace, add post-
      children mount-hook for safe SelectionChanged wiring, ImperativeBridged
      for tab strip slots.
  2. Overlay descriptor ports + registration — ContentDialog, Flyout,
    MenuBar, MenuFlyout, CommandBar, CommandBarFlyout, Popup. Modal
    lifecycle needs a new decorator strategy variant or
    IDecoratorElementHandler extension.
  3. NavigationHostElement port + registration — internal-expose
    MountNavigationHost / UpdateNavigationHost, duplicate cleanup
    logic in V1 handler so UnmountRecursive's intercept can be
    removed.
  4. XamlHost/XamlPage unification — reconcile V1 auto-reg with
    XamlInterop.Register to allow descriptor-driven path.

Remaining acceptance gates (deferred — call out for next PR)

  • ARM64 stable-AC ratification — per user direction, deferred to
    follow-up. Target capture date: 2026-05-28. To be landed under
    docs/specs/047/phase3-results/ before the next PR opens.
  • Perf 3×5 advisory re-capture — deferred to follow-up. Will land
    under
    docs/specs/047/phase3-results/CPC-ander-YTZ3O-x64-advisory/<date>-phase3-completion-3x5/
    with a README explaining the delta vs.
    2026-05-28-phase3-finish-3x5/.

Files changed

  • src/Reactor/Core/Reconciler.cs — RegisterV1BuiltInHandlers
    sweep + two helpers + carve-list XML doc.
  • docs/specs/047-extensible-control-model.md — §14 status
    update reflecting Phase 3 completion + remaining carve list.
  • docs/specs/tasks/047-extensible-control-model-implementation.md
    — same.

Earlier in branch (already in main..HEAD before this commit):

  • ~22 descriptor additions (Batches 1–4) + engine extension
    • PreMountedItems strategy + 1 fixture fix.

codemonkeychris and others added 26 commits May 28, 2026 11:09
…pView descriptor

Adds the engine-gap closer carried forward from Phase 3 finish: the typed
`TemplatedFlipViewElement<T>` peer now routes through V1 dispatch via a
new `PreMountedItems<TElement, TControl>` ChildrenStrategy and a
base-derived `TemplatedFlipViewDescriptor` registered on
`TemplatedFlipViewElementBase`.

Engine
- IItemsBinderStrategy.Bind signature widened to take `Element? oldElement`
  (null on Mount, set on Update). Keeps the consolidated dispatch arm in
  V1HandlerAdapter + DescriptorHandler to a single is-check + interface call.
  All 4 existing implementers ignore the new param (they read prior state
  from the control) — only the new PreMountedItems<> uses it.
- PreMountedItems<TElement, TControl> in ChildrenStrategy.cs: pre-mounts every
  item up-front through IItemViewSource into the control's IList<object> Items
  sink, and on Update positionally reconciles via Reconciler.ReconcileV1Child
  for shared slots, appending new tail slots and truncating excess. Rubber-duck
  recommendations adopted: Debug.Assert on (oldElement is TElement) and on
  `items.Count == oldSource.ItemCount` with release fallback to full rebuild
  if the invariant breaks; throws InvalidOperationException (not silent null
  writes) if mount/reconcile returns null in a slot.

Descriptor
- TemplatedFlipViewDescriptor uses PreMountedItems<> + HandCodedControlled
  for SelectedIndex with the existing FlipViewEventPayload shared trampoline
  slot. `callback` returns a synthetic non-null delegate gated on
  `el.HasCallbacks` so the engine only subscribes when at least one closed-T
  leaf has wired OnSelectedIndexChanged.
- Registered base-derived in RegisterV1BuiltInHandlers via
  RegisterHandlerForDerivedTypes<TemplatedFlipViewElementBase, FlipView>.

Tests
- New Desc_TemplatedFlipView_MountUpdate covers: pre-mount item-count,
  pre-mounted slots are UIElements, initial SelectedIndex applied, mount
  didn't fire callback, programmatic SelectedIndex write echo-suppressed,
  grow/shrink/edit-in-place positional reconcile, shrink clamps SelectedIndex,
  same-ref idempotency, edit-in-place preserves slot identity (CanUpdate path
  through ReconcileV1Child).
- New Desc_TemplatedFlipView_NoCallback_DoesNotSubscribe covers the
  HasCallbacks=false trampoline-not-subscribed branch.

Docs
- §14 carry-forwards: TemplatedFlipView engine-gap closed; updated stale
  `TemplatedFlipViewDescriptor stays carved` comment in FlipViewDescriptor.

Validation
- dotnet test tests/Reactor.Tests -c Release -p:Platform=x64 → 9134 ok / 0 fail
- --self-test --filter `Desc_` V1 ON = 630 ok / 0 fail; V1 OFF = 630 ok /
  0 fail (parity holds, both flags +17 vs Phase 3 finish baseline of 613 for
  the new TemplatedFlipView fixtures).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a SemanticElement descriptor backed by SemanticPanel with SingleContent child reconciliation and descriptor selftest coverage for mount/update parity.

Validated with: dotnet build src\Reactor\Reactor.csproj -c Release -p:Platform=x64; dotnet build tests\Reactor.AppTests.Host\Reactor.AppTests.Host.csproj -c Release -p:Platform=x64; dotnet run --project tests\Reactor.AppTests.Host -c Release -p:Platform=x64 --no-build -- --self-test --filter Desc_Semantic

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Port the untyped GridViewElement path to a V1 descriptor using ItemsHost and descriptor-managed selection/item-click events. Add the descriptor selftest and registry entries for mount/update coverage.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Port ItemContainerElement to a V1 single-content descriptor. Add descriptor selftest coverage for mount, child update, and IsSelected update.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a descriptor for the AnnounceRegionElement hidden TextBlock live-region anchor and register descriptor selftest coverage for mount/update parity.

Validated with: dotnet build src\Reactor\Reactor.csproj -c Release -p:Platform=x64; dotnet build tests\Reactor.AppTests.Host\Reactor.AppTests.Host.csproj -c Release -p:Platform=x64; dotnet run --project tests\Reactor.AppTests.Host -c Release -p:Platform=x64 --no-build -- --self-test --filter Desc_AnnounceRegion

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Port ItemsViewElementBase to a base-derived V1 descriptor using TemplatedItemsErased. Extend the erased keyed binder to reproduce the legacy ItemsView ItemsSource/ItemTemplate shape and add descriptor selftest coverage for mount/update and property changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Defer IconElement: the current V1 descriptor engine requires a stable TControl identity, while IconElement's legacy path chooses different native IconElement subclasses from IconData and can replace the native control when the IconData subtype changes. This needs an element-aware polymorphic factory or an approved replacement strategy before it can route through _v1Handlers without semantic drift.

Defer XamlHostElement: its factory returns arbitrary user-owned FrameworkElement instances, so the descriptor model cannot express the control type, pooling policy, or ownership safely through a single new() TControl descriptor.

Defer XamlPageElement: it is closer to descriptor-shaped than XamlHost, but the bridge still delegates to Frame.Navigate for arbitrary user XAML page types. The attempted descriptor selftest could not provide a deterministic code-only page target without crashing the WinUI navigation path, so this stays carved for orchestrator review rather than landing an unvalidated descriptor.

Validated with: dotnet build tests\Reactor.AppTests.Host\Reactor.AppTests.Host.csproj -c Release -p:Platform=x64; dotnet run --project tests\Reactor.AppTests.Host -c Release -p:Platform=x64 --no-build -- --self-test --filter Desc_Semantic; dotnet run --project tests\Reactor.AppTests.Host -c Release -p:Platform=x64 --no-build -- --self-test --filter Desc_AnnounceRegion

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Selftest: Desc_AnimatedVisualPlayer_MountUpdate (V1 ON).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Selftest: Desc_AnnotatedScrollBar_MountUpdate (V1 ON).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Selftest: Desc_MapControl_MountUpdate (V1 ON descriptor availability only). MapControl construction process-terminates this headless host without the Maps runtime/token, so E2E must own real lifecycle coverage.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Selftest: Desc_ParallaxView_MountUpdate (V1 ON).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Selftest: Desc_RefreshContainer_MountUpdate (V1 ON).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Selftest: Desc_SwipeControl_MountUpdate (V1 ON).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Selftest: Desc_SemanticZoom_MountUpdate (V1 ON).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Selftest: Desc_MediaPlayerElement_MountUpdate (V1 ON, no media source).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Selftest: Desc_WebView2_MountUpdate (V1 ON, no Source to avoid async CoreWebView2 init).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Selftest: Desc_TitleBar_MountUpdate (V1 ON). SetTitleBar is deferred to Loaded so the title bar is attached before window registration.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Selftest: Desc_NavigationView_MountUpdate (V1 ON). Selection trampoline handles built-in settings selection by reporting a null SelectedTag because WinUI does not expose a NavigationViewItem tag for Settings.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Batch 3 SemanticZoom fixture referenced a non-existent ListViewDescriptor
for its inner ZoomedInView/ZoomedOutView ListViewElement children. Use
the Phase 1 hand-coded ListViewHandler instead (the canonical V1
registration for ListView).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ension

Adds a new decorator-style V1 handler shape for elements whose returned
UIElement identity may change on update or whose unmount disposition
diverges from the standard pool-return. Targets the 8 elements deferred
during Phase 3 batches:

  - Target-wrapping decorators: FlyoutElement, MenuFlyoutElement,
    CommandBarFlyoutElement (returned UIElement IS the user's inner
    Target child — type isn't known until inspection).
  - Modal lifecycle wrappers: ContentDialogElement, PopupElement
    (returned control is a placeholder; the actual modal surface is
    side-mounted or lazy).
  - Polymorphic mounts: IconElement (concrete control type depends
    on the element's runtime subtype).
  - Interop bridges: XamlHostElement, XamlPageElement.

Surface changes (3 minimum-correct items per rubber-duck spec):

  1. IV1HandlerEntry.Update returns UIElement (was void). Standard
     handlers (V1HandlerAdapter) always return `control` unchanged,
     preserving §13 Q12's no-substitution invariant on the standard
     IElementHandler surface. Reconciler.Update threads the result
     into the parent slot via the existing legacy-registry path
     (already handled by ReconcileV1Child).

  2. IV1HandlerEntry.Unmount returns V1UnmountDisposition enum
     (CollectSelf | ContinueDefaultTraversal | SkipPool). Standard
     handlers always return CollectSelf, matching pre-extension
     behavior. Reconciler.UnmountRecursive + UnmountAndCollect switch
     on the returned disposition to control pool collection and
     traversal recursion.

  3. New IDecoratorElementHandler<TElement> contract (TElement only,
     no TControl) + V1DecoratorHandlerAdapter<TElement> bridging into
     IV1HandlerEntry. Distinct from IElementHandler<TElement,TControl>
     so the public author-facing surface retains its no-substitution
     invariant; only built-in V1 ports use this decorator shape.

     Registered via Reconciler.RegisterDecoratorHandler<TElement>
     (internal — built-in ports only).

No descriptor registrations changed in this commit. Descriptor ports
for the 8 deferred elements follow in subsequent commits.

Validation:
  - dotnet build src/Reactor/Reactor.csproj: 0 errors
  - dotnet build tests/Reactor.AppTests.Host: 0 errors
  - --self-test --filter Desc_ V1 ON: 728 ok / 0 failures
  - --self-test --filter Desc_ V1 OFF: 728 ok / 0 failures
  - Desc_ parity: ON ≡ OFF (no regression introduced by widening).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…duction V1 dispatch

Wires every Phase 3 descriptor authored across batches 1–4 (plus the
Phase 1 hand-coded handlers + base-derived templated/lazy/items hosts +
PreMountedItems TemplatedFlipView) into RegisterV1BuiltInHandlers, the
sole built-in V1 registration site. With this PR, V1 ON becomes the
production dispatch path for ~78 element types; only the carve-list
elements documented in the RegisterV1BuiltInHandlers XML doc still fall
through to the legacy Mount/Update switch.

Carve list (intentionally NOT registered — documented inline):
  - Composition primitives (Component, Func, Memo, ErrorBoundary,
    CommandHost, Validation.*) — sit ABOVE the V1 handler protocol
  - Interop bridges (XamlHost, XamlPage) — descriptors exist but
    XamlInterop.Register populates _typeRegistry at startup, would
    clash with EnsureRegistrableElementType; unification = Phase 4
  - Deferred overlays (ContentDialog, Flyout, MenuBar, MenuFlyout,
    CommandBar, CommandBarFlyout, Popup) — require decorator-style
    ports for modal lifecycle; follow-up PR
  - Deferred stateful host (NavigationHost) — per-instance route/cache
    state intercepted in UnmountRecursive before V1 dispatch arm;
    needs small refactor; follow-up PR
  - TabViewDescriptor — bisect (3× clean V1 ON full selftest with
    only this carved, vs. 1–4 random docking-text-find failures per
    run when registered) ratifies the descriptor's documented gaps:
    spec 045 §2.4 drag pipeline (OnTabDragStarting/Completed), §2.2
    pinnable headers (BuildTabHeader/BuildPinButton/in-place
    TryUpdatePinHeaderInPlace), in-place CanUpdate for tab content
    (preserves focus/state on re-renders), conditional SelectedIndex
    write, TabStripHeader/Footer Element slots. Closing requires
    engine work (post-children mount-hook so SelectionChanged
    subscribes after children-add + ImperativeBridged for named tab
    strip slots); tracked alongside overlays + NavigationHost.

Validation:
  - dotnet test tests/Reactor.Tests -c Release -p:Platform=x64:
    Passed!  9134 / 0 failed / 62 skipped
  - dotnet run --project tests/Reactor.AppTests.Host --self-test
    V1 ON (run 1): 4410 ok, 0 failures
    V1 ON (run 2): 4410 ok, 0 failures
    V1 OFF:         4410 ok, 0 failures
  - Desc_ filter A|B: 744 / 744 (perfect parity)

Helpers added (internal, [Experimental(`REACTOR_V1_PREVIEW'')]):
  RegisterDescriptor<TElement, TControl>(descriptor)
  RegisterDescriptorForDerivedTypes<TBase, TControl>(descriptor)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ompletion

Document Phase 3 completion in both the spec (§14) and the implementation
tracker. With the engine extension (IDecoratorElementHandler) +
PreMountedItems strategy + descriptor batches + production registration
sweep landed:

- Engine gap closed (TemplatedFlipView via PreMountedItems)
- Every Phase 3 batch descriptor authored AND registered:
  - Untyped items hosts: GridView, ItemsView, ItemContainer
  - Heavy/specialized: WebView2, NavigationView, TitleBar,
    MediaPlayerElement, AnimatedVisualPlayer, MapControl,
    SemanticZoom, AnnotatedScrollBar, RefreshContainer,
    SwipeControl, ParallaxView
  - Polymorphic/a11y: IconElement (via new IDecoratorElementHandler),
    SemanticElement, AnnounceRegion

Intentional carve list (documented inline in RegisterV1BuiltInHandlers
and re-enumerated in both docs for the follow-up reviewer):

- Dialog/overlay family (ContentDialog, Flyout, Popup, MenuBar,
  MenuFlyout, CommandBar, CommandBarFlyout) — modal lifecycle needs
  decorator-style ports beyond the IDecoratorElementHandler shape
- NavigationHost — UnmountRecursive intercepts before V1 arm
- TabViewDescriptor — bisect-ratified gaps need engine work
  (post-children mount-hook for safe SelectionChanged wiring +
  ImperativeBridged for tab strip slots)
- XamlHost/XamlPage — XamlInterop.Register clashes with V1 auto-reg
- Composition primitives (Component, Func, Memo, ErrorBoundary,
  CommandHost, Validation.*) — sit ABOVE V1 protocol; Phase 4 keeps
  their legacy arms

A|B parity bar met: 9134 xunit + 4410 selftest (V1 ON ≡ V1 OFF),
0 failures both flags across 3 consecutive full V1 ON runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…80% arms; 76/87 = 87% of V1-reachable)

Updates §14 in both the spec and tracker to reference PR #440 and add an explicit coverage table breaking the 95 legacy switch arms into 76 routed / 11 reachable-deferred / 8 permanent composition-primitive carves, plus the enumerated path-to-100% for the follow-up PR.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
case FontIconData fi when icon is WinUI.FontIcon fontIcon:
fontIcon.Glyph = fi.Glyph;
if (fi.FontFamily is not null)
fontIcon.FontFamily = new FontFamily(fi.FontFamily);
await Harness.Render();

H.Check("Desc_Semantic_UpdatedValue", panel.SemanticValue == "4 of 5");
H.Check("Desc_Semantic_UpdatedRange", panel.RangeValue == 4);
await Harness.Render();

H.Check("Desc_AnnotatedScrollBar_Mounted", true);
H.Check("Desc_AnnotatedScrollBar_InitialWidth", asb.Width == 48);
var el2 = AnnotatedScrollBar().Set(c => c.Width = 72);
rec.UpdateChild(el1, el2, asb, _noOp);
await Harness.Render();
H.Check("Desc_AnnotatedScrollBar_UpdatedWidth", asb.Width == 72);
await Harness.Render();

H.Check("Desc_ParallaxView_Mounted", true);
H.Check("Desc_ParallaxView_InitialVerticalShift", pv.VerticalShift == 12);
var el2 = ParallaxView(TextBlock("updated"), verticalShift: 24, horizontalShift: 6);
rec.UpdateChild(el1, el2, pv, _noOp);
await Harness.Render();
H.Check("Desc_ParallaxView_UpdatedVerticalShift", pv.VerticalShift == 24);
await Harness.Render();

H.Check("Desc_WebView2_Mounted", true);
H.Check("Desc_WebView2_InitialWidth", wv.Width == 320);
var el2 = WebView2().Set(c => c.Width = 480);
rec.UpdateChild(el1, el2, wv, _noOp);
await Harness.Render();
H.Check("Desc_WebView2_UpdatedWidth", wv.Width == 480);
H.Check("Desc_Semantic_Mounted", true);
H.Check("Desc_Semantic_Role", panel.SemanticRole == "slider");
H.Check("Desc_Semantic_Value", panel.SemanticValue == "3 of 5");
H.Check("Desc_Semantic_Range", panel.RangeMinimum == 0 && panel.RangeMaximum == 5 && panel.RangeValue == 3);
H.Check("Desc_Semantic_Mounted", true);
H.Check("Desc_Semantic_Role", panel.SemanticRole == "slider");
H.Check("Desc_Semantic_Value", panel.SemanticValue == "3 of 5");
H.Check("Desc_Semantic_Range", panel.RangeMinimum == 0 && panel.RangeMaximum == 5 && panel.RangeValue == 3);
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR completes Spec 047 §14 Phase 3 wiring by moving many built-in element types onto the V1 handler/descriptor dispatch path, while documenting the remaining carved legacy paths.

Changes:

  • Registers Phase 1 handlers, Phase 3 descriptors, base-derived descriptor handlers, and the Icon decorator handler in RegisterV1BuiltInHandlers.
  • Adds decorator-handler/unmount-disposition support and PreMountedItems<> for templated FlipView.
  • Adds descriptor fixtures and updates spec/task documentation with Phase 3 completion status and remaining carve list.
Show a summary per file
File Description
src/Reactor/Core/Reconciler.cs Expands built-in V1 registrations and unmount disposition handling.
src/Reactor/Core/Reconciler.Update.cs Allows V1 update handlers to return replacement controls.
src/Reactor/Core/Reconciler.KeyedItemsBinding.cs Adds keyed binding support for ItemsView.
src/Reactor/Core/Element.cs Makes ItemsViewElementBase expose keyed item source behavior.
src/Reactor/Core/V1HandlerRegistry.cs Updates V1 handler entry contract for update return/unmount disposition.
src/Reactor/Core/V1Protocol/V1HandlerAdapter.cs Returns controls from standard updates and forwards old elements to binders.
src/Reactor/Core/V1Protocol/V1DecoratorHandlerAdapter.cs Adds adapter for decorator-style V1 handlers.
src/Reactor/Core/V1Protocol/IDecoratorElementHandler.cs Defines decorator handler API and unmount disposition enum.
src/Reactor/Core/V1Protocol/ChildrenStrategy.cs Extends item binder contract and adds PreMountedItems<>.
src/Reactor/Core/V1Protocol/ControlEventPayloads.cs Adds payload slots for new descriptor event trampolines.
src/Reactor/Core/V1Protocol/Descriptor/DescriptorHandler.cs Passes old elements into item binder strategies.
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/AnimatedVisualPlayerDescriptor.cs Adds descriptor for AnimatedVisualPlayer.
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/AnnotatedScrollBarDescriptor.cs Adds descriptor for AnnotatedScrollBar.
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/AnnounceRegionDescriptor.cs Adds descriptor for live-region announcement anchor.
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/FlipViewDescriptor.cs Updates documentation for templated FlipView handling.
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/GridViewDescriptor.cs Adds descriptor for plain GridViewElement.
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/IconDescriptor.cs Adds decorator-style descriptor for polymorphic icons.
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/ItemContainerDescriptor.cs Adds descriptor for ItemContainerElement.
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/ItemsViewDescriptor.cs Adds descriptor for typed ItemsView elements.
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/MapControlDescriptor.cs Adds descriptor for MapControl.
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/MediaPlayerElementDescriptor.cs Adds descriptor for MediaPlayerElement.
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/NavigationViewDescriptor.cs Adds descriptor for NavigationView.
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/ParallaxViewDescriptor.cs Adds descriptor for ParallaxView.
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/RefreshContainerDescriptor.cs Adds descriptor for RefreshContainer.
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/SemanticDescriptor.cs Adds descriptor for accessibility semantic wrapper.
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/SemanticZoomDescriptor.cs Adds descriptor for SemanticZoom.
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/SwipeControlDescriptor.cs Adds descriptor for SwipeControl.
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/TemplatedFlipViewDescriptor.cs Adds descriptor using PreMountedItems<>.
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/TitleBarDescriptor.cs Adds descriptor for TitleBar.
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/WebView2Descriptor.cs Adds descriptor for WebView2.
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/XamlHostDescriptor.cs Adds unregistered decorator descriptor for XamlHost.
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/XamlPageDescriptor.cs Adds unregistered decorator descriptor for XamlPage.
tests/Reactor.AppTests.Host/SelfTest/Fixtures/Spec047V1ProtocolDescriptorFixtures.cs Adds descriptor selftest coverage for new ports.
tests/Reactor.AppTests.Host/SelfTest/SelfTestFixtureRegistry.cs Registers new selftest fixtures.
docs/specs/047-extensible-control-model.md Updates Phase 3 completion status and coverage counts.
docs/specs/tasks/047-extensible-control-model-implementation.md Updates task status, deferred scope, and cleanup guidance.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 36/36 changed files
  • Comments generated: 4

Comment on lines +48 to +52
Children = new ItemsHost<GridViewElement, WinUI.GridView>(
GetItems: static e => (IReadOnlyList<object>)e.Items,
GetCollection: static c => c.Items),
GetSetters = static e => e.Setters,
}
shouldWrite: static e => !double.IsNaN(e.ExpandedModeThresholdWidth))
.Imperative(
mount: static (c, e) => ApplyMenuAndSelection(c, oldElement: null, e),
update: static (c, o, n) => ApplyMenuAndSelection(c, o, n))
Comment on lines +570 to +577
// Collection / source-count drift would corrupt positional pairing.
// Debug-assert; release falls through to the same rebuild path as
// the type-mismatch case via the count clamp below.
global::System.Diagnostics.Debug.Assert(
items.Count == oldCount,
"PreMountedItems<>: control items collection (" + items.Count
+ ") drifted from previous source ItemCount (" + oldCount + ").");
if (items.Count != oldCount) oldCount = items.Count;
protocol — Phase 4 cleanup keeps their legacy arms):**

- `ComponentElement`, `FuncElement`, `MemoElement`,
`ErrorBoundaryElement`, `CommandHostElement`, `ModifiedElement`,
* Carve GridViewDescriptor — its ItemsHost<> strategy pre-mounts every
  item into GridView.Items (no virtualization), diverging from legacy
  MountGridView's ItemsSource+CCC lazy realization. A|B tests still
  pass, but production memory/lifecycle would silently regress; closing
  needs hand-coded GridViewHandler or RecyclingItemsHost<> shape.
  Updated coverage table: 75 routed / 12 deferred / 8 primitives.

* Align legacy UpdateNavigationView with V1 NavigationViewDescriptor —
  add PaneDisplayMode, IsSettingsVisible, conditional PaneTitle,
  MenuItems rebuild on ref-change, and SelectedItem re-selection
  (with new FindNavItemByTag helper). Previously legacy only wrote
  a subset of these on update; reviewer correctly flagged the A|B
  divergence for record-with updates that change MenuItems / Selected.

* Fix PreMountedItems<> release fallback at ChildrenStrategy.cs:577 —
  the count-drift path was clamping oldCount=items.Count and continuing
  positional reconciliation, which would index past oldSource bounds
  when items.Count > oldSource.ItemCount or skip stale source items'
  teardown when smaller. Replaced with the same full-rebuild path as
  the type-mismatch release fallback above.

* Doc fix: remove ModifiedElement from the tracker's composition-
  primitive carve list — it's unwrapped before V1/legacy dispatch at
  the top of Reconciler.Mount, not a switch arm; coverage table stays
  at 8 primitives.

Validation: dotnet test 9134/0; full V1 ON selftest 4410/0;
full V1 OFF selftest 4410/0 (each suite required one re-run to clear
the known FloatRoot/Reliability/DockHooks docking flake family).

Ignored 9 github-code-quality bot comments: 8 are integer-valued
double == checks in test fixtures (values are assigned constants,
exact equality is reliable); 1 claims WinUI FontFamily is IDisposable
(it is not).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@codemonkeychris codemonkeychris merged commit f0d3e81 into main May 28, 2026
15 checks passed
@codemonkeychris codemonkeychris deleted the spec/047-phase3-completion branch May 28, 2026 21:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants